/*
 * Solo - A small and beautiful blogging system written in Java.
 * Copyright (c) 2010-present, b3log.org
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */
;(function ($) {
    $.fn.circleMagic = function (options) {

        var width, height, canvas, ctx, animateHeader = true;
        var circles = [];

        var settings = $.extend({
            color: 'rgba(255,255,255,.5)',
            radius: 10,
            density: 0.3,
            clearOffset: 0.2
        }, options);

        //  Main

        var container = this['0'];
        initContainer();
        addListeners();

        function initContainer() {
            width = container.offsetWidth;
            height = container.offsetHeight;

            //  create canvas element

            initCanvas();
            canvas = document.getElementById('canvas');
            canvas.width = width;
            canvas.height = height;
            canvas.style.position = 'absolute';
            canvas.style.left = '0';
            canvas.style.bottom = '0';
            ctx = canvas.getContext('2d');

            //  create circles
            for (var x = 0; x < width * settings.density; x++) {
                var c = new Circle();
                circles.push(c);
            }
            animate();
        }

        //Init canvas element
        function initCanvas() {
            var canvasElement = document.createElement('canvas');
            canvasElement.id = 'canvas';
            container.appendChild(canvasElement);
            canvasElement.parentElement.style.overflow = 'hidden';

        }

        // Event handling
        function addListeners() {
            window.addEventListener('resize', resize, false);
        }

        function resize() {
            width = container.clientWidth;
            height = container.clientHeight;
            container.height = height + 'px';
            canvas.width = width;
            canvas.height = height;
        }

        function animate() {
            if (animateHeader) {
                ctx.clearRect(0, 0, width, height);
                for (var i in circles) {
                    circles[i].draw();
                }
            }
            requestAnimationFrame(animate);
        }

        function randomColor() {
            var r = Math.floor(Math.random() * 255);
            var g = Math.floor(Math.random() * 255);
            var b = Math.floor(Math.random() * 255);
            var alpha = Math.random().toPrecision(2);
            return 'rgba(' + r + ', ' + g + ', ' + b + ', ' + alpha + ')';
        }

        //  Canvas manipulation

        function Circle() {
            var that = this;

            // constructor
            (function () {
                that.pos = {};
                init();
            })();

            function init() {
                that.pos.x = Math.random() * width;
                that.pos.y = height + Math.random() * 100;
                that.alpha = 0.1 + Math.random() * settings.clearOffset;
                that.scale = 0.1 + Math.random() * 0.3;
                that.speed = Math.random();
                if (settings.color === 'random') {
                    that.color = randomColor();
                }
                else {
                    that.color = settings.color;
                }
            }

            this.draw = function () {
                if (that.alpha <= 0) {
                    init();
                }
                that.pos.y -= that.speed;
                that.alpha -= 0.0005;
                ctx.beginPath();
                ctx.arc(that.pos.x, that.pos.y, that.scale * settings.radius, 0, 2 * Math.PI, false);
                ctx.fillStyle = that.color;
                ctx.fill();
                ctx.closePath();
            };
        }
    }
})(jQuery);

/**
 * Ribbons Class File.
 * Creates low-poly ribbons background effect inside a target container.
 */
(function( name, factory )
{
    if( typeof window === "object" )
    {
        window[ name ] = factory();
    }

})( "Ribbons", function()
{
    var _w = window,
        _b = document.body,
        _d = document.documentElement;

    // random helper
    var random = function()
    {
        if( arguments.length === 1 ) // only 1 argument
        {
            if( Array.isArray( arguments[0] ) ) // extract index from array
            {
                var index = Math.round( random( 0, arguments[0].length - 1 ) );
                return arguments[0][ index ];
            }
            return random( 0, arguments[0] ); // assume numeric
        }
        else if( arguments.length === 2 ) // two arguments range
        {
            return Math.random() * ( arguments[1] - arguments[0] ) + arguments[0];
        }
        return 0; // default
    };

    // screen helper
    var screenInfo = function( e )
    {
        var width   = Math.max( 0, _w.innerWidth || _d.clientWidth || _b.clientWidth || 0 ),
            height  = Math.max( 0, _w.innerHeight || _d.clientHeight || _b.clientHeight || 0 ),
            scrollx = Math.max( 0, _w.pageXOffset || _d.scrollLeft || _b.scrollLeft || 0 ) - ( _d.clientLeft || 0 ),
            scrolly = Math.max( 0, _w.pageYOffset || _d.scrollTop || _b.scrollTop || 0 ) - ( _d.clientTop || 0 );

        return {
            width   : width,
            height  : height,
            ratio   : width / height,
            centerx : width / 2,
            centery : height / 2,
            scrollx : scrollx,
            scrolly : scrolly,
        };
    };

    // mouse/input helper
    var mouseInfo = function( e )
    {
        var screen = screenInfo( e ),
            mousex = e ? Math.max( 0, e.pageX || e.clientX || 0 ) : 0,
            mousey = e ? Math.max( 0, e.pageY || e.clientY || 0 ) : 0;

        return {
            mousex  : mousex,
            mousey  : mousey,
            centerx : mousex - ( screen.width / 2 ),
            centery : mousey - ( screen.height / 2 ),
        };
    };

    // point object
    var Point = function( x, y )
    {
        this.x = 0;
        this.y = 0;
        this.set( x, y );
    };
    Point.prototype = {
        constructor: Point,

        set: function( x, y )
        {
            this.x = ( x || 0 );
            this.y = ( y || 0 );
        },
        copy: function( point )
        {
            this.x = ( point.x || 0 );
            this.y = ( point.y || 0 );
            return this;
        },
        multiply: function( x, y )
        {
            this.x *= ( x || 1 );
            this.y *= ( y || 1 );
            return this;
        },
        divide: function( x, y )
        {
            this.x /= ( x || 1 );
            this.y /= ( y || 1 );
            return this;
        },
        add: function( x, y )
        {
            this.x += ( x || 0 );
            this.y += ( y || 0 );
            return this;
        },
        subtract: function( x, y )
        {
            this.x -= ( x || 0 );
            this.y -= ( y || 0 );
            return this;
        },
        clampX: function( min, max )
        {
            this.x = Math.max( min, Math.min( this.x, max ) );
            return this;
        },
        clampY: function( min, max )
        {
            this.y = Math.max( min, Math.min( this.y, max ) );
            return this;
        },
        flipX: function()
        {
            this.x *= -1;
            return this;
        },
        flipY: function()
        {
            this.y *= -1;
            return this;
        },
    };

    // class constructor
    var Factory = function( options )
    {
        this._canvas  = null;
        this._context = null;
        this._sto     = null;
        this._width   = 0;
        this._height  = 0;
        this._scroll  = 0;
        this._ribbons = [];
        this._options = {
            // ribbon color HSL saturation amount
            colorSaturation: "80%",
            // ribbon color HSL brightness amount
            colorBrightness: "60%",
            // ribbon color opacity amount
            colorAlpha: 0.65,
            // how fast to cycle through colors in the HSL color space
            colorCycleSpeed : 6,
            // where to start from on the Y axis on each side (top|min, middle|center, bottom|max, random)
            verticalPosition : "center",
            // how fast to get to the other side of the screen
            horizontalSpeed : 150,
            // how many ribbons to keep on screen at any given time
            ribbonCount: 3,
            // add stroke along with ribbon fill colors
            strokeSize: 0,
            // move ribbons vertically by a factor on page scroll
            parallaxAmount : -0.5,
            // add animation effect to each ribbon section over time
            animateSections : true,
        };
        this._onDraw   = this._onDraw.bind( this );
        this._onResize = this._onResize.bind( this );
        this._onScroll = this._onScroll.bind( this );
        this.setOptions( options );
        this.init();
    };

    // class prototype
    Factory.prototype = {
        constructor: Factory,

        // Set and merge local options
        setOptions: function( options )
        {
            if( typeof options === "object" )
            {
                for( var key in options )
                {
                    if( options.hasOwnProperty( key ) )
                    {
                        this._options[ key ] = options[ key ];
                    }
                }
            }
        },

        // Initialize the ribbons effect
        init: function()
        {
            try
            {
                this._canvas = document.createElement( "canvas" );
                this._canvas.style["display"] = "block";
                this._canvas.style["position"] = "fixed";
                this._canvas.style["margin"] = "0";
                this._canvas.style["padding"] = "0";
                this._canvas.style["border"] = "0";
                this._canvas.style["outline"] = "0";
                this._canvas.style["left"] = "0";
                this._canvas.style["top"] = "0";
                this._canvas.style["width"] = "100%";
                this._canvas.style["height"] = "100%";
                this._canvas.style["z-index"] = "-1";
                this._onResize();

                this._context = this._canvas.getContext( "2d" );
                this._context.clearRect( 0, 0, this._width, this._height );
                this._context.globalAlpha = this._options.colorAlpha;

                window.addEventListener( "resize", this._onResize );
                document.body.appendChild( this._canvas );
            }
            catch( e ) {
                console.warn( "Canvas Context Error: " + e.toString() );
                return;
            }
            this._onDraw();
        },

        // Create a new random ribbon and to the list
        addRibbon: function()
        {
            // movement data
            var dir    = ( Math.round( random( 1, 9 ) ) > 5 ) ? "right" : "left",
                stop   = 1000,
                hide   = 200,
                min    = 0 - hide,
                max    = this._width + hide,
                movex  = 0,
                movey  = 0,
                startx = ( dir === "right" ) ? min : max,
                starty = Math.round( random( 0, this._height ) );

            // asjust starty based on options
            if( /^(top|min)$/i.test( this._options.verticalPosition ) )
            {
                starty = 0 + hide;
            }
            else if( /^(middle|center)$/i.test( this._options.verticalPosition ) )
            {
                starty = ( this._height / 2 );
            }
            else if( /^(bottom|max)$/i.test( this._options.verticalPosition ) )
            {
                starty = this._height - hide;
            }

            // ribbon sections data
            var ribbon = [],
                point1 = new Point( startx, starty ),
                point2 = new Point( startx, starty ),
                point3 = null,
                color  = Math.round( random( 0, 360 ) ),
                delay  = 0;

            // buils ribbon sections
            while( true )
            {
                if( stop <= 0 ) break; stop--;

                movex = Math.round( ( Math.random() * 1 - 0.2 ) * this._options.horizontalSpeed );
                movey = Math.round( ( Math.random() * 1 - 0.5 ) * ( this._height * 0.25 ) );

                point3 = new Point();
                point3.copy( point2 );

                if( dir === "right" )
                {
                    point3.add( movex, movey );
                    if( point2.x >= max ) break;
                }
                else if( dir === "left" )
                {
                    point3.subtract( movex, movey );
                    if( point2.x <= min ) break;
                }
                // point3.clampY( 0, this._height );

                ribbon.push({ // single ribbon section
                    point1 : new Point( point1.x, point1.y ),
                    point2 : new Point( point2.x, point2.y ),
                    point3 : point3,
                    color  : color,
                    delay  : delay,
                    dir    : dir,
                    alpha  : 0,
                    phase  : 0,
                });

                point1.copy( point2 );
                point2.copy( point3 );

                delay += 4;
                color += this._options.colorCycleSpeed;
            }
            this._ribbons.push( ribbon );
        },

        // Draw single section
        _drawRibbonSection: function( section )
        {
            if( section )
            {
                if( section.phase >= 1 && section.alpha <= 0 )
                {
                    return true; // done
                }
                if( section.delay <= 0 )
                {
                    section.phase += 0.02;
                    section.alpha = Math.sin( section.phase ) * 1;
                    section.alpha = ( section.alpha <= 0 ) ? 0 : section.alpha;
                    section.alpha = ( section.alpha >= 1 ) ? 1 : section.alpha;

                    if( this._options.animateSections )
                    {
                        var mod = ( Math.sin( 1 + section.phase * Math.PI / 2 ) * 0.1 );

                        if( section.dir === "right" )
                        {
                            section.point1.add( mod, 0 );
                            section.point2.add( mod, 0 );
                            section.point3.add( mod, 0 );
                        } else {
                            section.point1.subtract( mod, 0 );
                            section.point2.subtract( mod, 0 );
                            section.point3.subtract( mod, 0 );
                        }
                        section.point1.add( 0, mod );
                        section.point2.add( 0, mod );
                        section.point3.add( 0, mod );
                    }
                }
                else { section.delay -= 0.5; }

                var s = this._options.colorSaturation,
                    l = this._options.colorBrightness,
                    c = "hsla("+ section.color +", "+ s +", "+ l +", "+ section.alpha +" )";

                this._context.save();

                if( this._options.parallaxAmount !== 0 )
                {
                    this._context.translate( 0, this._scroll * this._options.parallaxAmount );
                }
                this._context.beginPath();
                this._context.moveTo( section.point1.x, section.point1.y );
                this._context.lineTo( section.point2.x, section.point2.y );
                this._context.lineTo( section.point3.x, section.point3.y );
                this._context.fillStyle = c;
                this._context.fill();

                if( this._options.strokeSize > 0 )
                {
                    this._context.lineWidth = this._options.strokeSize;
                    this._context.strokeStyle = c;
                    this._context.lineCap = "round";
                    this._context.stroke();
                }
                this._context.restore();
            }
            return false; // not done yet
        },

        // Draw ribbons
        _onDraw: function()
        {
            // cleanup on ribbons list to rtemoved finished ribbons
            for( var i = 0, t = this._ribbons.length; i < t; ++i )
            {
                if( !this._ribbons[ i ] )
                {
                    this._ribbons.splice( i, 1 );
                }
            }

            // draw new ribbons
            this._context.clearRect( 0, 0, this._width, this._height );

            for( var a = 0; a < this._ribbons.length; ++a ) // single ribbon
            {
                var ribbon = this._ribbons[ a ],
                    numSections = ribbon.length,
                    numDone = 0;

                for( var b = 0; b < numSections; ++b ) // ribbon section
                {
                    if( this._drawRibbonSection( ribbon[ b ] ) )
                    {
                        numDone++; // section done
                    }
                }
                if( numDone >= numSections ) // ribbon done
                {
                    this._ribbons[ a ] = null;
                }
            }
            // maintain optional number of ribbons on canvas
            if( this._ribbons.length < this._options.ribbonCount )
            {
                this.addRibbon();
            }
            requestAnimationFrame( this._onDraw );
        },

        // Update container size info
        _onResize: function( e )
        {
            var screen   = screenInfo( e );
            this._width  = screen.width;
            this._height = screen.height;

            if( this._canvas )
            {
                this._canvas.width  = this._width;
                this._canvas.height = this._height;

                if( this._context )
                {
                    this._context.globalAlpha = this._options.colorAlpha;
                }
            }
        },

        // Update container size info
        _onScroll: function( e )
        {
            var screen   = screenInfo( e );
            this._scroll = screen.scrolly;
        },

    };

    // export
    return Factory;
});